17.6 监控
尽管有控制器、三色标记等一系列措施,但垃圾回收器依然有问题需要解决。
模拟场景:服务重启,海量客户端重新接入,瞬间分配大量对象,这会将垃圾回收的触发条件next_gc推到一个很大值。而当服务正常后,因活跃对象远小于该阈值,造成垃圾回收久久无法触发,服务进程内就会有大量白色对象无法被回收,造成隐性内存泄漏。同样的情形也可能是因为某个算法在短期内大量使用临时对象造成的。
用示例来模拟一下:
test.go
package main
import( “fmt” “runtime” “time” )
func test() { type M[1<<10]byte data:=make([]M,102420)
// 申请20MB内存分配。超出初始阈值,将next_gc推高
for i:=range data{ data[i] =new(M) }
// 解除引用,预防内联导致data生命周期变长
for i:=range data{ data[i] =nil } }
func main() { test()
for{ var ms runtime.MemStats runtime.ReadMemStats(&ms) fmt.Printf(“%s%d MB\n”,time.Now().Format(“15:04:05”),ms.NextGC>>20)
time.Sleep(time.Second*30)
}
}
编译执行:
$go build-gcflags”-l” -o test test.go
$GODEBUG=“gctrace=1” ./test
gc 1@0.005s 15%: …,6→6→6 MB,4 MB goal,2 P gc 2@0.016s 8%: …,8→8→8 MB,13 MB goal,2 P gc 3@0.022s 9%: …,14→14→14 MB,17 MB goal,2 P 09:36:01 26 MB 09:36:31 26 MB 09:37:01 26 MB 09:37:31 26 MB 09:38:01 26 MB GC forced gc 4@120.037s 0%: …,20→20→0 MB,29 MB goal,2 P scvg0:inuse:0,idle:20,sys:21,released:0,consumed:21(MB) 09:38:31 4 MB 09:39:01 4 MB
我们用test函数来模拟短期内大量分配对象的行为。输出结果表明,在其结束后的相当长时间内都没有触发垃圾回收。直到forcegc介入,才将next_gc恢复正常。
这就是垃圾回收器最后的一道保险措施。监控服务sysmon每隔2分钟就会检查一次垃圾回收状态,如超出2分钟未曾触发,那就强制执行。
proc1.go
func sysmon() { // 如果超过2分钟未曾做垃圾回收,那么强制执行 forcegcperiod:=int64(2601e9)
for{ …
// 最后一次回收时间
lastgc:=int64(atomicload64(&memstats.last_gc))
if lastgc!=0&&unixnow-lastgc>forcegcperiod&&
atomicload(&forcegc.idle) !=0&&atomicloaduint(&bggc.working) ==0{
// 将forcegc goroutine放到待运行队列
injectglist(forcegc.g)
}
} }
和前文bgsweep goroutine一样,forcegc goroutine也是死循环、休眠、等待唤醒模式。
proc.go
func init() { go forcegchelper() }
func forcegchelper() { forcegc.g=getg() for{ atomicstore(&forcegc.idle,1)
// 休眠待唤醒
goparkunlock(&forcegc.lock, "force gc(idle)",traceEvGoBlock,1)
// 参数forceTrigger=true,让gc不检查next_gc值,直接执行
startGC(gcBackgroundMode,true)
} }